﻿using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace Konveyer.Test.RunnerTests
{
    using FluentAssertions;

    using Konveyer.Core;
    using Konveyer.Core.Attributes;
    using Konveyer.Core.Interfaces;
    using System.Linq;
    using Moq;

    [TestClass]
    public class Given_a_Runner
    {
        public IRunner TestRunner { get; set; }
        public Mock<IInstanceProvider> InstanceProvider { get; set; }

        [TestInitialize]
        public void OnStartUp()
        {
            this.InstanceProvider = new Mock<IInstanceProvider>();
            var runner = new Runner(this.InstanceProvider.Object);
            this.TestRunner = runner;
        }



        [TestMethod]
        public void it_is_not_null()
        {
            // act 

            // arrange 

            // assert 
            TestRunner.Should().NotBeNull();

        }

        [TestMethod]
        public void it_can_accept_a_request()
        {
            // act 
            var request = new Moq.Mock<IRequest>().Object;

            // arrange 
            TestRunner.With(request);

            // assert 
            TestRunner.Should().NotBeNull();
        }

        [TestMethod]
        public void it_can_accept_an_onRequestStart_listener()
        {
            // act 
            var request = new Moq.Mock<IRequest>().Object;
            Action<IRequest> onRequestStart = (IRequest req) => { };

            // arrange 
            TestRunner.With(request).WhenStarts(onRequestStart);

            // assert 
            TestRunner.Should().NotBeNull();
        }


        [TestMethod]
        public void it_can_call_onRequestStart_listener()
        {
            // act 
            var called = false;
            var request = new Moq.Mock<IRequest>().Object;
            Action<IRequest> onRequestStart = (IRequest req) => { called = true; };
            TestRunner.With(request).WhenStarts(onRequestStart);

            // arrange 
            TestRunner.With(request).WhenStarts(onRequestStart).Execute();

            // assert 
            called.Should().BeTrue();
        }

        [TestMethod]
        public void it_can_accept_an_onRequestEnd_listener()
        {
            // act           
            Action<IRequest> onRequestEnd = (IRequest req) => { };

            // arrange 
            TestRunner.With(new MockRequest_A()).WhenEnds(onRequestEnd);

            // assert 
            TestRunner.Should().NotBeNull();
        }


        [TestMethod]
        public void it_can_call_onRequestEnd_listener()
        {
            // act 
            var called = false;
            Action<IRequest> onRequestEnd = (IRequest req) => { called = true; };


            // arrange 
            TestRunner.With(new MockRequest_A()).WhenEnds(onRequestEnd).Execute();

            // assert 
            called.Should().BeTrue();
        }

        [TestMethod]
        public void it_can_call_execute()
        {
            // act     

            // arrange 
            TestRunner.With(new MockRequest_A()).Execute();

            // assert 

        }

        [TestMethod]
        public void it_can_call_execute_with_a_request()
        {
            // act 


            // arrange 
            TestRunner.Execute(new MockRequest_A());

            // assert 

        }


        [TestMethod]
        public void Widget_specified_in_the_belt_are_requested()
        {
            // arrange 
            var request = new MockRequest_A();

            // act
            TestRunner.Execute(request);

            // assert 
            this.InstanceProvider.Verify(x => x.Get(typeof(MockWidget_1)), Times.Once);

        }

        [TestMethod]
        public void Requests_go_through_all_step_in_the_correct_order()
        {
            // arrange 
            var currentStep = -1;
            var count = 0;
            var request = new MockRequest_A();
            request.OnCurrentStepSet = (m) =>
            {
                if ((int)m.CurrentStep > currentStep)
                {
                    count++;
                }
            };

            var values = Enum.GetValues(typeof(OperationStep));


            // act 
            TestRunner.Execute(request);

            // assert 
            Assert.AreEqual(values.Length, count);
        }


        [TestMethod]
        public void Requests_are_passed_to_all_Widgets()
        {
            var name = "some name";
            // arrange 
            var mockWidget1 = new MockWidget_1();
            var mockWidget2 = new MockWidget_2();
            var mockWidget3 = new MockWidget_3();

            this.InstanceProvider.Setup(x => x.Get(typeof(MockWidget_1))).Returns(mockWidget1);
            this.InstanceProvider.Setup(x => x.Get(typeof(MockWidget_2))).Returns(mockWidget2);
            this.InstanceProvider.Setup(x => x.Get(typeof(MockWidget_3))).Returns(mockWidget3);

            var request = new MockRequest_B();
            request.Name = name;

            // act 
            TestRunner.Execute(request);

            // assert 
            Assert.AreEqual(request.Name, mockWidget1.Request.Name);
            Assert.AreEqual(request.Name, mockWidget2.Request.Name);
            Assert.AreEqual(request.Name, mockWidget3.Request.Name);
        }


        [TestMethod]
        public void Exceptions_stop_later_widgets_running()
        {
            // arrange 
            var mockWidget1 = new MockWidget_1(new InvalidTimeZoneException());
            var mockWidget2 = new MockWidget_2();
            var mockWidget3 = new MockWidget_3();

            this.InstanceProvider.Setup(x => x.Get(typeof(MockWidget_1))).Returns(mockWidget1);
            this.InstanceProvider.Setup(x => x.Get(typeof(MockWidget_2))).Returns(mockWidget2);
            this.InstanceProvider.Setup(x => x.Get(typeof(MockWidget_3))).Returns(mockWidget3);

            var request = new MockRequest_B();

            // act 
            TestRunner.Execute(request);

            // assert             
            mockWidget2.Request.Should().BeNull();
            mockWidget3.Request.Should().BeNull();
        }


        [TestMethod]
        public void Exceptions_should_fire_listeners()
        {
            // arrange 
            var mockWidget1 = new MockWidget_1(new InvalidTimeZoneException());
            IRequest onExceptionRequest = null;
            Action<IRequest> onException = (i) => { onExceptionRequest = i; };

            this.InstanceProvider.Setup(x => x.Get(typeof(MockWidget_1))).Returns(mockWidget1);

            var request = new MockRequest_B();

            // act 
            TestRunner.WhenExceptionIsThrown(onException).Execute(request);

            // assert 
            onExceptionRequest.Should().NotBeNull();

        }

        [TestMethod]
        public void no_request_will_throw_an_ArgumentException()
        {
            // arrange 

            var request = new MockRequest_B();

            ArgumentException ex = null;

            // act 
            try
            {
                TestRunner.Execute();
            }
            catch (Exception e)
            {
                ex = e as ArgumentException;
            }



            // assert 
            ex.Should().NotBeNull();

        }
    }

    class MockWidget : IWidget
    {
        public MockRequest Request { get; set; }

        public virtual void DoWork(IRequest request)
        {
            Request = request as MockRequest;
        }
    }

    class MockWidget_1 : MockWidget
    {
        Exception _ex;
        public MockWidget_1(Exception ex = null)
        {
            _ex = ex;
        }

        public override void DoWork(IRequest request)
        {
            base.DoWork(request);
            if (_ex != null)
                throw _ex;
        }
    }


    class MockWidget_2 : MockWidget
    {

    }

    class MockWidget_3 : MockWidget
    {

    }


    [Belt(Step = OperationStep.Validation,
        Description = "Some Description",
        Widget = typeof(MockWidget_1))]
    abstract class MockRequest : IRequest
    {
        public String Name { get; set; }

        public Action<MockRequest> OnCurrentStepSet { get; set; }

        private OperationStep _currentStep;

        public OperationStep CurrentStep
        {
            get { return _currentStep; }
            set
            {
                if (OnCurrentStepSet != null)
                {
                    OnCurrentStepSet(this);
                }
                _currentStep = value;
            }

        }
        public Exception Exception { get; set; }
    }


    class MockRequest_A : MockRequest
    {

    }


    [Belt(Step = OperationStep.Operation,
        Description = "Some Description",
        Widget = typeof(MockWidget_2))]

    [Belt(Step = OperationStep.Logging,
        Description = "Some Description",
        Widget = typeof(MockWidget_3))]
    class MockRequest_B : MockRequest
    {

    }

}
